#pragma once

#include "../utils/misc.h"
#include "../utils/pack.h"

namespace mdtx::online::updater
{
    template <typename Tin, typename Tout, size_t Nin, size_t Nout>
    class updater_base
    {
    private:
        constexpr static make_index_range<0, Nin> newidx{};
        constexpr static make_index_range<Nin, Nin + Nin> oldidx{};

        template <typename TUPLE, size_t... Is>
        inline void call_insert(TUPLE const &tup, std::index_sequence<Is...>)
        {
            insert(tuple_in(std::get<Is>(tup)...));
        }

        template <typename TUPLE, size_t... Is>
        inline void call_remove(TUPLE const &tup, std::index_sequence<Is...>)
        {
            remove(tuple_in(std::get<Is>(tup)...));
        }

    public:
        // input scala type
        using scala_in = Tin;
        // output scala type
        using scala_out = Tout;
        // input tuple
        using tuple_in = ntuple<Tin, Nin>;
        // output tuple
        using tuple_out = ntuple<Tout, Nout>;
        // number of input
        constexpr static size_t num_in = Nin;
        // number of output
        constexpr static size_t num_out = Nout;

        /**
         * @brief Insert new datum
         *
         */
        virtual void insert(tuple_in const &) = 0;

        template <typename... T>
        void insert(T... x)
        {
            static_assert(sizeof...(T) == Nin, "Wrong number of input.");
            insert(tuple_in(x...));
        }

        /**
         * @brief Remove/evict old datum
         *
         */
        virtual void remove(tuple_in const &) {}

        template <typename... T>
        void remove(T... x)
        {
            static_assert(sizeof...(T) == Nin, "Wrong number of input.");
            remove(tuple_in(x...));
        }

        // TODO: when called from base pointer, roll() could be slower
        template <typename... T>
        void roll(T... x)
        {
            static_assert(sizeof...(T) == Nin + Nin, "Wrong number of input.");
            auto tup = std::tuple(x...);
            call_insert(tup, newidx);
            call_remove(tup, oldidx);
        }

        /**
         * @brief Reset to initial state
         *
         */
        virtual void reset() = 0;

        /**
         * @brief Get output
         *
         * @return tuple_out
         */
        virtual tuple_out get() = 0;

        /**
         * @brief Get output and reset to initial state
         *
         * @return tuple_out
         */
        tuple_out pop()
        {
            tuple_out tup = get();
            reset();
            return tup;
        }

        virtual ~updater_base() = default;
    };

    template <typename T>
    using counter_base1 = updater_base<T, ptrdiff_t, 1, 1>;

    template <typename T>
    using counter_base2 = updater_base<T, ptrdiff_t, 1, 2>;

    template <typename T>
    using updater_unary1 = updater_base<T, T, 1, 1>;

    template <typename T>
    using updater_unary2 = updater_base<T, T, 1, 2>;

    template <typename T>
    using updater_weighted1 = updater_base<T, T, 2, 1>;

    template <typename T>
    using updater_weighted2 = updater_base<T, T, 2, 2>;

    template <typename T>
    using updater_binary1 = updater_base<T, T, 2, 1>;

    template <typename T>
    using updater_binary2 = updater_base<T, T, 2, 2>;
}
